# -*- coding: utf-8 -*-
import psutil
import logging
import os
import json
import threading

from app.module.TaskWorker import TaskWorker
from app.module.TaskQueue import TaskQueue
from app.utils.exception import TaskParamsFormatError, MenUsedUpToMaxError, TaskHasExistError, TaskNotFoundError, \
    TaskNotInitError, DataBaseError
from app.models.model import PublishAPIServer
logger = logging.getLogger(__name__) # 返回一个新的以文件名为名的logger


def check_illage(task_info):
    """
    判断参数时候合法
    :param task_info:
    :return:
    """
    if not ('task_id' in task_info.keys()):
        return 0
    if not ('task_type' in task_info.keys()):
        return 0
    if not ('model_name' in task_info.keys()):
        return 0
    if not ('model_path' in task_info.keys()):
        return 0
    if not ('variable_name' in task_info.keys()):
        return 0
    # if not os.path.isfile(task_info['model_path']):
    #     return 0
    return 1


def get_used_men():
    data = psutil.virtual_memory()
    return data.percent


class MachineState(object):
    def __init__(self, app=None, db=None):
        self.app = app
        self.db = db
        self.default_mem_use_max_per = 85
        self.task_queue = TaskQueue()
        self._server_id = None
        self._value_lock = threading.Lock()  #线程锁, 用于锁住添加和删除时的队列操作
        if app is not None:
            self.init_app(app)

    def init_app(self, app=None, db=None):
        default_mem_use_max_per = app.config.get("MEM_USED_MAX_PER")
        server_id = app.config.get("SERVER_ID")
        model_path = app.config.get("MODEL_PATH")
        SCP_USE = app.config.get("SCP_USE")
        if default_mem_use_max_per is not None:
            self.default_mem_use_max_per = default_mem_use_max_per
        if server_id is not None:
            self._server_id = server_id
        else:
            raise AssertionError("server_id不能够为空, 请在yaml文件配置server_id")

        try:
            if db is not None:
                task_list = db.session.query(PublishAPIServer).filter(PublishAPIServer.server_id == self._server_id).all()
                if task_list is None:
                    self.task_queue = TaskQueue()

                for i in range(0, len(task_list)):
                    task_id = str(task_list[i].task_id)
                    task_type = task_list[i].task_type
                    model_name = task_list[i].model_name
                    variable_name = task_list[i].variable_name.split(",")
                    labels = None
                    if task_list[i].labels is not None and task_list[i].labels.strip() != '' and \
                            task_list[i].labels.lower() != 'none':
                        labels = json.loads(task_list[i].labels)

                    feature = None
                    if task_list[i].feature is not None and task_list[i].feature.strip() != '' and \
                            task_list[i].feature.lower() != 'none':
                        feature = json.loads(task_list[i].feature)

                    date_name = None
                    if task_list[i].date_name is not None and task_list[i].date_name.strip() != '' and \
                            task_list[i].date_name.lower() != 'none':
                        date_name = task_list[i].date_name.split(",")

                    (filepath, modelfilename) = os.path.split(model_name)
                    if(SCP_USE==0):
                        model_abs_path = model_name
                    else:
                        model_abs_path = os.path.join(model_path, modelfilename)
                    task_worker = TaskWorker(task_id, task_type, model_name, model_abs_path, variable_name, labels, \
                                             feature, date_name)
                    if SCP_USE==1:
                        try:
                            if not os.path.exists(model_abs_path):
                                if hasattr(app, "extensions"):
                                    if app.extensions["scp_client"] is not None:
                                        app.extensions["scp_client"].download(model_name, model_abs_path)
                            task_worker.init_model()
                            self.task_queue.put(task_worker)
                        except Exception as e:
                            logging.error(e)
                            logging.error(model_name+"模型下载存在问题，"+str(task_id)+"不再加载了")
                    else:
                        task_worker.init_model()
                        self.task_queue.put(task_worker)
                    # task_worker.init_model()
                    # self.task_queue.put(task_worker)

                logger.info("从数据库读取信息成功，已初始化了任务队列")
            else:
                logger.info("数据库信息为空, 初始化的预测任务队列将会为空")
        except Exception as e:
            print(e)
            logger.error("读取数据库信息的时候出错了, 预测任务队列将会为空")
            self.task_queue = TaskQueue()

        if not hasattr(app, "extensions"):
            app.extensions = {}

        app.extensions["machine_state"] = self

    def update_predict_task(self, db, predict_task, status, error):
        predict_task.status = status
        predict_task.error = error
        db.session.commit()

    def is_can_add(self, task_info):
        """
        判断当前服务器是否允许添加API服务,如果允许会返回True, 否则会报相应的一场
        :param task_info:
        :return:
        """
        is_illage = check_illage(task_info)
        if not is_illage:
            raise TaskParamsFormatError("任务参数格式错误，请稍后再试")
        else:
            men_used = get_used_men()
            if men_used > self.default_mem_use_max_per:
                raise MenUsedUpToMaxError("内存使用达到阈值")
            else:
                return True

    def is_exist(self, task_id):
        for i in range(0, len(self.task_queue.get_list())):
            task = self.task_queue.get_item(i)
            if task is None:
                continue

            if task_id == task.get_task_id():
                raise TaskHasExistError(task_id)

        return False

    def add_task(self, task_info):
        """
        添加预测任务
        :param task_info:
        :return:
        """
        if self.is_can_add(task_info):
            if not self.is_exist(task_info["task_id"]):
                task_worker = TaskWorker(task_info["task_id"], task_info["task_type"], task_info["model_name"], \
                                         task_info["model_path"], task_info["variable_name"], task_info["labels"],  \
                                         task_info["feature"], task_info['date_name'])
                task_worker.init_model()
                if self.db is not None:
                    try:
                        date_name_str = None
                        if task_info['date_name'] is not None:
                            date_name_str = ",".join(map(str, task_info["date_name"]))

                        api_info = PublishAPIServer(
                            task_id=task_info["task_id"],
                            server_id=self._server_id,
                            task_type=task_info["task_type"],
                            model_name=task_info["model_name"],
                            variable_name=",".join(map(str, task_info["variable_name"])),
                            labels=str(json.dumps(task_info["labels"])),
                            feature=str(json.dumps(task_info["feature"])),
                            date_name=date_name_str,
                        )
                        self.db.session.add(api_info)
                        self.db.session.commit()
                    except Exception as e:
                        raise DataBaseError("数据库出问题了")
                with self._value_lock:
                    self.task_queue.put(task_worker)

    def stop_task(self, task_id):
        """
        停止预测任务
        :param task_id: 任务Id
        :return:
        """
        with self._value_lock:
            for i in range(0, self.task_queue.get_length()):
                task = self.task_queue.get_item(i)
                if task is None:
                    continue

                if str(task_id) == str(task.get_task_id()):
                    if self.db is not None:
                        self.db.session.query(PublishAPIServer).filter(PublishAPIServer.task_id == task_id).delete(synchronize_session=False)
                        self.db.session.commit()
                    self.task_queue.delete(i)
                    return True

            raise TaskNotFoundError("任务Id: " + str(task_id) + " 没有找到相应的任务")

    def predict_task(self, task_id, predict_params):
        """
        预测任务预测
        :param task_id: 任务Id
        :param predict_params: 预测参数
        :return:
        """

        for i in range(0, self.task_queue.get_length()):
            task = self.task_queue.get_item(i)
            if task is None:
                continue

            if str(task_id) == str(task.get_task_id()):
                if not task.get_is_init():
                    raise TaskNotInitError("任务Id: " + str(task_id) + " 预测任务未有初始化，请停止或者重建任务")

                predict_result = task.predict(predict_params)
                return predict_result

        raise TaskNotFoundError("任务Id: " + str(task_id) + " 没有找到相应的任务")



    def predictOnetask(self, task_id, predict_params):
        """
        预测任务预测
        :param task_id: 任务Id
        :param predict_params: 预测参数
        :return:
        """

        for i in range(0, self.task_queue.get_length()):
            task = self.task_queue.get_item(i)
            if task is None:
                continue

            if str(task_id) == str(task.get_task_id()):
                if not task.get_is_init():
                    raise TaskNotInitError("任务Id: " + str(task_id) + " 预测任务未有初始化，请停止或者重建任务")

                predict_result = task.predictOne(predict_params)
                return predict_result

        raise TaskNotFoundError("任务Id: " + str(task_id) + " 没有找到相应的任务")


    def recreate(self, task_id, model_name=None, model_path=None, variable_name=None, labels=None, feature=None, date_name=None):
        for i in range(0, self.task_queue.get_length()):
            task = self.task_queue.get_item(i)
            if task is None:
                continue

            if str(task_id) == str(task.get_task_id()):
                task.recreate(model_name=model_name, model_path=model_path, variable_name=variable_name, labels=labels, \
                              feature=feature, date_name=date_name)
                update_value = {}
                if model_name is not None:
                    update_value["model_name"] = model_name

                if variable_name is not None:
                    update_value["variable_name"] = variable_name

                if labels is not None:
                    update_value["labels"] = str(json.dumps(labels))

                if feature is not None:
                    update_value["feature"] = str(json.dumps(feature))

                if feature is not None:
                    update_value["date_name"] = date_name

                self.db.session.query(PublishAPIServer).filter(PublishAPIServer.task_id == task_id). \
                    update(update_value)
                self.db.session.flush()
                self.db.session.commit()

                return

        raise TaskNotFoundError("任务Id: " + str(task_id) + " 没有找到相应的任务")

    def get_task_list(self):
        task_list = self.task_queue.get_list()
        result_list = []
    
        for i in range(0, len(task_list)):
            task = self.task_queue.get_item(i)
            if task is None:
                continue
            if not task.get_is_init():
                continue
            task_info = dict()
            task_info["task_id"] = task.get_task_id()
            result_list.append(task_info)

        return result_list
